package com.opencee.boot.autoconfigure.web;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.opencee.common.converter.DateConverter;
import com.opencee.common.converter.XssStringConverter;
import com.opencee.common.converter.XssStringJsonDeserializer;
import com.opencee.common.exception.DefaultWebExceptionAdvice;
import com.opencee.common.filter.DefaultModifyRequestFilter;
import com.opencee.common.utils.HtmlTemplateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.annotation.Order;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
import org.thymeleaf.spring5.SpringTemplateEngine;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;

/**
 * @author liuyadu
 */
@Slf4j
@ConditionalOnWebApplication
@Configuration
@Order(200)
public class DefaultWebMvcConfiguration implements WebMvcConfigurer {

    public DefaultWebMvcConfiguration() {
        log.info("默认WebMvc配置@Order(200):如需创建多个WebMvcConfigurer,设置小于@Order(200)");
        log.info("默认Locale环境:{},请求参数:https://url?lang={}", LocaleContextHolder.getLocale(), LocaleContextHolder.getLocale());
    }

    @Value("${server.servlet.session.cookie.name:_sid}")
    private String cookieName;

    @Value("${spring.profiles.active}")
    private String env;

    private final static List<String> IGNORE_ENV = Arrays.asList(new String[]{"online", "prod"});

    /**
     * 默认解析器 其中locale表示默认语言
     */
    @Bean
    public LocaleResolver localeResolver() {
        AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
        // 默认区域
        localeResolver.setDefaultLocale(Locale.CHINA);
        return localeResolver;
    }

    /**
     * 修改sessionCookie
     *
     * @return
     */
    @Bean
    public ServletContextInitializer servletContextInitializer() {
        return new ServletContextInitializer() {
            @Override
            public void onStartup(ServletContext servletContext) throws ServletException {
                String sessionCookie = cookieName;
                // 区分不用环境sessionCookie
                if (StringUtils.isNotBlank(env) && !IGNORE_ENV.contains(env)) {
                    sessionCookie = sessionCookie + "_" + env;
                }
                servletContext.getSessionCookieConfig().setName(sessionCookie);
            }
        };

    }

    /**
     * 统一异常处理配置
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(DefaultWebExceptionAdvice.class)
    public DefaultWebExceptionAdvice globalExceptionHandler() {
        DefaultWebExceptionAdvice exceptionHandler = new DefaultWebExceptionAdvice();
        log.info("全局Web异常处理器 [{}]", exceptionHandler);
        return exceptionHandler;
    }

    /**
     * 支持body缓存
     * 修改请求头等
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean baseModifyRequestFilter() {
        DefaultModifyRequestFilter filter = new DefaultModifyRequestFilter();
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(filter);
        filterRegistrationBean.setOrder(1);
        log.info("请求装饰器 [{}]", filter);
        return filterRegistrationBean;
    }


    /**
     * 默认objectMapper配置
     *
     * @return
     */
    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        // 枚举处理
        objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
        // 排序key
        objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, false);
        //关闭日期序列化为时间戳的功能
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        //关闭序列化的时候没有为属性找到getter方法,报错
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        //关闭反序列化的时候，没有找到属性的setter报错
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        //反序列化的时候如果多了其他属性,不抛出异常
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        //如果是空对象的时候,不抛异常
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        //开启反序列化,可以使用空String将null映射到POJO. String不是POJO,它被视为JSON原始值.
        objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
        SimpleModule simpleModule = new SimpleModule();
        //自定义序列化过滤配置(XssStringJsonDeserializer), 对入参进行转译
        simpleModule.addDeserializer(String.class, new XssStringJsonDeserializer());
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        simpleModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
        // Jackson序列化将null转为空字符串和DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT完美配合。
        objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
            @Override
            public void serialize(Object o, JsonGenerator jsonGenerator,
                                  SerializerProvider serializerProvider)
                    throws IOException, JsonProcessingException {
                jsonGenerator.writeString("");
            }
        });
        objectMapper.registerModule(simpleModule);
        log.info("ObjectMapper [{}]", objectMapper);
        return objectMapper;
    }

    /**
     * 资源处理器
     *
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/favicon.ico")
                .addResourceLocations("classpath:/static/");
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/");
        registry.addResourceHandler("swagger-ui.html", "doc.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }


    /**
     * 自定义日期转换器
     *
     * @param registry
     */
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new DateConverter());
        registry.addConverter(new XssStringConverter());
    }


    @Configuration
    @AutoConfigureAfter({ThymeleafAutoConfiguration.class})
    static class TemplateConfiguration {
        @Bean
        public HtmlTemplateUtil HtmlTemplateUtil(SpringTemplateEngine springTemplateEngine) {
            HtmlTemplateUtil pdfUtil = new HtmlTemplateUtil(springTemplateEngine);
            log.info("Html工具类 [{}] 模板引擎:[{}]", pdfUtil, springTemplateEngine);
            return pdfUtil;
        }
    }
}
